#include "bureaucrat.h"
#include "registrar.h"
#include "schedule.h"
#include "schedulesorter.h"
#include "coursefactory.h"
#include <algorithm>
#include "algorithm.h"
#include <string>
#include <sstream>
#include "course.h"
#include "context.h"
#include "logger.h"

/// The one and only Bureaucrat instance.  Since it is a static member, it must
/// be defined, not only declared.  The C++ standard states that static
/// variables are set to 0 unless specified otherwise.  We rely on this here,
/// because when the instance_ pointer is null we know it was never initialized.
Bureaucrat* Bureaucrat::instance_;

/// pimpl definition.
struct BureaucratImpl {
};

/// Default constructor.
Bureaucrat::Bureaucrat()
	: pimpl_(new BureaucratImpl())
{
}

/// Destructor:  Because the pimpl is a boost::shared_ptr, there is no
/// need to delete it.
Bureaucrat::~Bureaucrat()
{
    delete instance_;
}

/// Gain access to the one-and-only Bureaucrat instance.
/// \return The one-and-only Bureaucrat instance.
Bureaucrat* Bureaucrat::instance()
{
    if( instance_ == 0 )
		instance_ = new Bureaucrat();
	return instance_;
}

/// Helper predicate which determines whether a given course is a "start
/// course".  A "start course" is a course which has no prerequisites.
bool is_start_node(const Course& course)
{
    Context context(Registrar::instance()->student());
	return course.prerequisites() == 0 ||
        course.prerequisites()->count(context) == 0;
}

/// Given a set of sorted courses, remove the courses the student has already
/// completed.
void remove_completed_courses(std::vector<Course> &courses)
{
    using namespace std;
    
    // get completed courses
    Student student = Registrar::instance()->student();
    vector<shared_ptr<Course> > completed_p = student.getCompletedCourses();
    
    // transform vector of pointers to vector of the dereferenced pointers
    vector<Course> completed;
    for( vector<shared_ptr<Course> >::iterator i = completed_p.begin();
         i != completed_p.end(); ++i ) {
        completed.push_back(**i);
    }

    // find the courses that are not complete by the student
    sort(courses.begin(), courses.end());
    sort(completed.begin(), completed.end());
    vector<Course> courses_to_do(courses.size());
    courses_to_do.erase(
        set_difference(courses.begin(), courses.end(),
                       completed.begin(), completed.end(),
                       courses_to_do.begin()));

    // copy the non-completed courses back into original vector.
    vector<Course>().swap(courses);
    copy(courses_to_do.begin(), courses_to_do.end(), back_inserter(courses));
}

/// Generate/recover the next schedule.
std::vector<Course> Bureaucrat::sequence() const
{
	using namespace std;
	ostringstream oss;

	typedef std::vector<Course> Courses;
	//typedef std::vector<Dependency> Dependencies;
	
	Courses sequence; // L:  output sequence
	Courses start_courses; // Q:  list of courses with fulfilled requirements

    // get courses from course factory
    Courses courses;
    CourseFactory::pool_range pool(CourseFactory::instance()->pool());
    for( CourseFactory::pool_type::const_iterator i(pool.first);
         i != pool.second;
         ++i ) {
        courses.push_back(*(i->second));
    };
    
    remove_completed_courses(courses);

    // prepare for copying from courses to start_courses;
    // start_courses must be of size big enough prior to copy
    Courses::difference_type number_of_start_courses =
        std::count_if(courses.begin(), courses.end(), is_start_node);
    start_courses.resize(number_of_start_courses);
	oss << "courses.size() == " << courses.size();
    Logger::instance()->message(oss.str(), 3);
    oss.str("");
    
	copy_if(courses.begin(),
			courses.end(),
			start_courses.begin(),
			is_start_node);

    Student student = Registrar::instance()->student();
    Context context(student);

	oss << start_courses.size() << " start courses";
    Logger::instance()->message(oss.str(), 3);
    oss.str("");

	while( ! start_courses.empty() ) {
 		sequence.push_back(start_courses.back());
		start_courses.pop_back();
		// for each dependent, assume it is not a dependent (remove it)
        std::list<Course> dependents = sequence.back().prerequisites()->courses();
		for( std::list<Course>::iterator i = dependents.begin();
			 i != dependents.end(); ++i ) {
            // mark requirement fulfilled
            context.check_requirement(CourseFactory::instance()->get(*i));
            // all requirements fulfilled?
            if( i->evaluate(context) )
				start_courses.push_back(*i);
		}
	}

	return sequence;
}

std::vector<Schedule> Bureaucrat::schedules() const
{
    Logger::instance()->warning("Entering Bureaucrat::schedules(), which should be refactored!", 2);
    std::size_t number_of_courses = Registrar::instance()->student().getCourseCount();
    if( number_of_courses == 0 ) {
        Logger::instance()->warning("No courses requested to be scheduled.  "
                                    "Ignoring.", 1);
        return std::vector<Schedule>();
    }
    std::ostringstream oss;
    oss << "Generating a schedule of " << number_of_courses << " courses.";
    Logger::instance()->message(oss.str(), 1);
    oss.str("");
    std::vector<Course> seq = sequence();
    if( seq.size() < number_of_courses )
        number_of_courses = seq.size();
	std::vector<Course> courses(seq.begin(), seq.begin() + number_of_courses);
    oss << "Courses to schedule:  ";
    std::copy(courses.begin(), courses.end(), std::ostream_iterator<Course>(oss, ";  "));
    Logger::instance()->message(oss.str(), 3);
    oss.str("");
    std::vector<Course*> coursesp;
	std::vector<Schedule> schedules;
	for( std::vector<Course>::iterator i = courses.begin();
         i != courses.end();
         ++i ) {
        coursesp.push_back(CourseFactory::instance()->get(*i).get());
    }
    Logger::instance()->warning("In Bureaucrat::schedules(), semester is hardcoded!", 1);
    scheduleSorter(coursesp, schedules, 4);
    return schedules;
}
